package main

import (
	"fmt"
	"math"
)

/*
https://tour.go-zh.org/methods/11

接口值
接口也是值。它们可以像其它值一样传递。

接口值可以用作函数的参数或返回值。

在内部，接口值可以看做包含值和具体类型的元组：

(value, type)
接口值保存了一个具体底层类型的具体值。

接口值调用方法时会执行其底层类型的同名方法。
*/

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	fmt.Println(t.S)
}

type T2 struct {
	S string
}

/*
https://tour.go-zh.org/methods/12

底层值为 nil 的接口值
即便接口内的具体值为 nil，方法仍然会被 nil 接收者调用。

在一些语言中，这会触发一个空指针异常，但在 Go 中通常会写一些方法来优雅地处理它（如本例中的 M 方法）。

注意: 保存了 nil 具体值的接口其自身并不为 nil。
*/

func (t *T2) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

type F float64

func (f F) M() {
	fmt.Println(f)
}

func main() {
	var i I

	i = &T{"Hello"}
	describe(i)
	i.M()
	i = &T2{"hello2"}
	describe(i)
	i.M()

	var t *T2
	i = t
	describe(i)
	i.M()

	i = F(math.Pi)
	describe(i)
	i.M()
	fmt.Println("---DemoBlankInterface 空接口---")
	DemoBlankInterface()

	/*
	https://tour.go-zh.org/methods/13
		nil 接口值
		nil 接口值既不保存值也不保存具体类型。

		为 nil 接口调用方法会产生运行时错误，因为接口的元组内并未包含能够指明该调用哪个 具体 方法的类型。
	*/
	fmt.Println("----nil 接口值既不保存值也不保存具体类型\n为 nil 接口调用方法会产生运行时错误，" +
		"因为接口的元组内并未包含能够指明该调用哪个 具体 方法的类型。")
	var i2 I
	describe(i2)
	fmt.Println("---i2.M()---")
	i2.M()
}

//func describe(i I) {
//	fmt.Printf("(%v, %T)\n", i, i)
//}

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}

/*
https://tour.go-zh.org/methods/14
空接口
指定了零个方法的接口值被称为 *空接口：*

interface{}
空接口可保存任何类型的值。（因为每个类型都至少实现了零个方法。）

空接口被用来处理未知类型的值。例如，fmt.Print 可接受类型为 interface{} 的任意数量的参数。
 */
func DemoBlankInterface() {

	var i interface{}
	describe(i)

	i = 42
	describe(i)

	i = "hello"
	describe(i)
}
